package com.shelpe.services.algorithm.mobileQscore;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.time.DateUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.shelpe.services.algorithm.client.SinbadClient;
import com.shelpe.services.algorithm.entity.batis.Keyword;
import com.shelpe.services.algorithm.entity.batis.KeywordExample;
import com.shelpe.services.algorithm.entity.batis.RptKeywordExample;
import com.shelpe.services.algorithm.entity.jpa.AutoProductQueue;
import com.shelpe.services.algorithm.entity.jpa.Campaign;
import com.shelpe.services.algorithm.entity.jpa.DevelopmentKeyword;
import com.shelpe.services.algorithm.entity.jpa.DevelopmentPlan;
import com.shelpe.services.algorithm.entity.jpa.UserLevelReport;
import com.shelpe.services.algorithm.entity.mongo.MobileQscoreActionLog;
import com.shelpe.services.algorithm.repository.batis.KeywordMapper;
import com.shelpe.services.algorithm.repository.batis.RptKeywordMapper;
import com.shelpe.services.algorithm.repository.jpa.AutoProductQueueRepository;
import com.shelpe.services.algorithm.repository.jpa.CampaignRepository;
import com.shelpe.services.algorithm.repository.jpa.DevelopmentKeywordRepository;
import com.shelpe.services.algorithm.repository.jpa.DevelopmentPlanRepository;
import com.shelpe.services.algorithm.repository.jpa.UserLevelReportRepository;
import com.shelpe.services.algorithm.repository.mongo.MobileQscoreActionLogRepository;
import com.shelpe.services.algorithm.service.RealtimeRankService;
import com.shelpe.services.algorithm.sustain.SustainRule;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class MobileQscoreSustainRule implements SustainRule<MobileQscoreSustainer> {
	@Autowired
	private AutoProductQueueRepository autoProductQueueRepository;
	@Autowired
	private RptKeywordMapper rptKeywordMapper;
	@Autowired
	private KeywordMapper keywordMapper;
	@Autowired
	private CampaignRepository campaignRepository;
	@Autowired
	private SinbadClient sinbadClient;
	@Autowired
	private RealtimeRankService realtimeRankService;
	@Autowired
	private List<SustainRule<MobileScoreKeywordSustainer>> sustainRules;
	@Autowired
	private DevelopmentPlanRepository developmentPlanRepository;
	@Autowired
	private DevelopmentKeywordRepository developmentKeywordRepository;
	@Autowired
	private MobileQscoreActionLogRepository mobileQscoreActionLogRepository;
	@Autowired
	private UserLevelReportRepository userLevelReportRepository;
	
	@Override
	public boolean check(MobileQscoreSustainer entity) {
		Date today = DateUtils.truncate(new Date(), Calendar.DATE);
		
		boolean isMobile = entity.getAdgroup().getDriveType() == 2; 
		long days = ((new Date()).getTime() - entity.getAdgroup().getCreateTime().getTime()) / (1000 * 60 * 60 *24);
		boolean first = (days <= 2);
		entity.setDays((int) days);
		
		boolean continued = false;
		if(entity.getAdgroup().getMaintainedDate() != null){
			long maintainDays = (today.getTime() - entity.getAdgroup().getMaintainedDate().getTime()) / (1000 * 60 * 60 *24);
			continued = (maintainDays > 0?true:(entity.getAdgroup().getAdjustCount() == 0));
		}else{
			continued = true;
		}
		boolean res =  isMobile && continued && "online".equals(entity.getAdgroup().getOnlineStatus());
		if(!res){
			log.debug("exit as: mobile qscore - {}, days - {}, status - {}", isMobile, days, "online".equals(entity.getAdgroup().getOnlineStatus()));
			return false;
		}
		Campaign campaign = this.campaignRepository.findOne(entity.getAdgroup().getCampaignID());
		res = campaign != null && "online".equals(campaign.getOnlineStatus());
		if(!res){
			log.debug("exit as: campaigin - {}", campaign);
			//set as next run if campaign is offline
			SinbadClient.AdgroupDto dto = new SinbadClient.AdgroupDto();
			dto.setDriveType(-1);
			dto.setMaintainedDate(new Date());
			this.sinbadClient.updateAdgroup(entity.getAdgroup().getAdgroupID(), dto);
		}
		
		entity.setFirstRun(first);
		return res;
	}

	@Override
	public boolean prepare(final MobileQscoreSustainer entity) {
		log.debug("prepare MobileQscoreSustainRule: {}", entity);
		KeywordExample example = new KeywordExample();
		example.setUserId(entity.getAdgroup().getUserID());
		example.createCriteria().andAdgroupIdEqualTo(entity.getAdgroup().getAdgroupID());
		entity.setKeywords(this.keywordMapper.selectByExample(example));
		if(log.isDebugEnabled() && entity.getKeywords() != null){
			log.debug("keywords: {}", ToStringBuilder.reflectionToString(entity.getKeywords()));
		}
		
		if(entity.getKeywords().size() == 0){
			return false;
		}
		
		AutoProductQueue queue = this.autoProductQueueRepository.findByCampaignIDAndProductID(entity.getAdgroup().getCampaignID(), 
				entity.getAdgroup().getProductID());
		entity.setQueue(queue);
		if(log.isDebugEnabled()){
			log.debug("queue: {}", queue);
		}
		
		int days = 1;
		Date today = DateUtils.truncate(new Date(), Calendar.DATE);
		RptKeywordExample ex = new RptKeywordExample();
		ex.setUserId(entity.getAdgroup().getUserID());
		RptKeywordExample.Criteria c = ex.createCriteria().andAdgroupIdEqualTo(entity.getAdgroup().getAdgroupID());
		c = c.andDateBetween(DateUtils.addDays(today, -days), today);
		c = c.andSourceBetween((byte) 4, (byte)5);
		entity.setLastReports(this.rptKeywordMapper.selectSumByExample(ex));
		
		if(log.isDebugEnabled() && entity.getLastReports() != null){
			log.debug("last {} days' states: {}", days, ToStringBuilder.reflectionToString(entity.getLastReports()));
		}
		
		List<Long> keywordIDs = new ArrayList<Long>();
		for(Keyword kw: entity.getKeywords()){
			keywordIDs.add(kw.getKeywordId());
		}
		entity.setRanks(realtimeRankService.syncRanks(entity.getAdgroup().getNick(), entity.getAdgroup().getAdgroupID(), keywordIDs));
		
		return entity.getQueue() != null && entity.getKeywords() != null && entity.getKeywords().size() > 0;
	}

	@Override
	public SustainRule.SustainStatus sustain(MobileQscoreSustainer entity) {
		SinbadClient.AdgroupDto dto = new SinbadClient.AdgroupDto();
		dto.setStatus(1);
		this.sinbadClient.updateAdgroupStatus(entity.getAdgroup().getAdgroupID(), dto);
		
		return SustainRule.SustainStatus.keep;
	}

	@Override
	public boolean maintain(MobileQscoreSustainer entity,
			SustainRule.SustainStatus status) {
		List<DevelopmentKeyword> dks = new ArrayList<DevelopmentKeyword>();
		List<Keyword> removingKeywords = new ArrayList<Keyword>();
		
		UserLevelReport report = this.userLevelReportRepository.findFirstByUserIDOrderByDateDesc(entity.getAdgroup().getUserID());
		log.debug("user level report: {}", report);
		
		MobileQscoreActionLog actionLog = new MobileQscoreActionLog();
		actionLog.from(new Date(), entity.getQueue(), entity.getAdgroup());
		actionLog.setDays(entity.getDays());
		
		//计算调整结果
		float totalCtr = 0;
		int countHasStr = 0;
		int totalMobileQscore = 0;
		int countHasMobileQscore = 0;
		SustainRule<MobileScoreKeywordSustainer> sustainRule = null;
		Map<String, Keyword> existingKeywords = new HashMap<String, Keyword>();
		for(Keyword kw: entity.getKeywords()){
			existingKeywords.put(kw.getWord(), kw);
			MobileScoreKeywordSustainer kwSustainer = new MobileScoreKeywordSustainer();
			kwSustainer.setKeyword(kw);
			kwSustainer.setAdgroup(entity.getAdgroup());
			kwSustainer.setQueue(entity.getQueue());
			kwSustainer.setLastReport(entity.findLastReport(kw.getKeywordId()));
			kwSustainer.setUserReport(report);
			kwSustainer.setRealtimeRank(entity.findRank(kw.getKeywordId()));
			kwSustainer.setSnapshot(new MobileQscoreActionLog.KeywordSnapshot());
			kwSustainer.getSnapshot().setKeywordID(kw.getKeywordId());
			kwSustainer.getSnapshot().setWord(kw.getWord());
			if(kw.getWirelessQscore() != null){
				kwSustainer.getSnapshot().setMobileQscore(kw.getWirelessQscore());
			}else{
				kwSustainer.getSnapshot().setMobileQscore(-100);
			}
			if(kwSustainer.getRealtimeRank() != null){
				kwSustainer.getSnapshot().setRealtimeRank(kwSustainer.getRealtimeRank().getMoibleRank());
			}
			sustainRule = this.findRule(kwSustainer);
			
			boolean toNext = sustainRule != null;
			if(toNext){
				toNext = sustainRule.prepare(kwSustainer);
				if(toNext){
					SustainRule.SustainStatus ss = sustainRule.sustain(kwSustainer);
					if(SustainRule.SustainStatus.ranking == ss){
						dks.add(kwSustainer.getDevelopmentKeyword());
					}else if(SustainRule.SustainStatus.remove == ss){
						removingKeywords.add(kw);
					}
					kwSustainer.getSnapshot().setAction(ss.name());
					toNext = sustainRule.maintain(kwSustainer, ss);
					if(toNext){
						toNext = sustainRule.mark(kwSustainer);
					}
				}
			}
			if(kwSustainer.getSnapshot().getAction() == null){
				kwSustainer.getSnapshot().setAction("notfound");
			}
			actionLog.addSnapshot(kwSustainer.getSnapshot());
			
			
			if(kwSustainer.getLastReport() != null && kwSustainer.getLastReport().getCtr() != null){
				totalCtr = totalCtr + kwSustainer.getLastReport().getCtr().floatValue();
				countHasStr = countHasStr + 1;
			}
			if(kwSustainer.getKeyword() != null && kwSustainer.getKeyword().getWirelessQscore() != null){
				totalMobileQscore = totalMobileQscore + kwSustainer.getKeyword().getWirelessQscore().intValue();
				countHasMobileQscore = countHasMobileQscore + 1;
			}
			log.debug("word({} - {}) sustain result: {}", kw.getKeywordId(), kw.getWord(), toNext);
		}
		if(countHasStr > 0){
			actionLog.setAverageCtr(totalCtr/countHasStr);
		}
		if(countHasMobileQscore > 0){
			actionLog.setAverageMobileQscore(totalMobileQscore/countHasMobileQscore);
		}
		
		//删除关键词
		Map<String, Keyword> removedKeywords = new HashMap<String, Keyword>();
		if(removingKeywords.size() > 0){
			List<Long> ids = new ArrayList<Long>();
			for(Keyword kw: removingKeywords){
				ids.add(kw.getKeywordId());
				existingKeywords.remove(kw.getWord());
				log.debug("removing keyword: {}", kw);
				removedKeywords.put(kw.getWord(), kw);
			}
			SinbadClient.ListResponse<SinbadClient.PricingKeyword> ret = this.sinbadClient.delKeywords(entity.getAdgroup().getCampaignID(), ids);
			log.debug("delete keywords ret: {}", ret);
			if(ret.getDatas() != null && ret.getDatas().size() > 0){
				if(removingKeywords.size() != ret.getDatas().size()){
					log.error("some keywords fail to deleted: {}", entity.getAdgroup());
				}
				for(SinbadClient.PricingKeyword pk:ret.getDatas()){
					MobileQscoreActionLog.KeywordSnapshot ks = actionLog.findByKeywordID(pk.getKeywordID());
					if(ks != null){
						ks.setResult(true);
					}
				}
			}else{
				log.error("no keyword is success to deleted: {}", entity.getAdgroup());
			}
			entity.setRemoved(true);
		}
		
		//生成培养计划
		if(dks.size() > 0){
			//pause previous developing plans if exist
			List<DevelopmentPlan> existingPlans = this.developmentPlanRepository.findByStatusAndToTimeAfterAndContainerID(DevelopmentPlan.Status.starting, 
					DateUtils.addDays(new Date(), -1), entity.getAdgroup().getAdgroupID());
			if(existingPlans.size() > 0){
				for(DevelopmentPlan dp: existingPlans){
					log.debug("pause the current developing plan: {}", dp);
					dp.setStatus(DevelopmentPlan.Status.finish);
				}
				this.developmentPlanRepository.save(existingPlans);
			}
			
			DevelopmentPlan plan = new DevelopmentPlan();
			plan.setContainerID(entity.getAdgroup().getAdgroupID());
			plan.setContainerType("adgroup");
			plan.setFromTime(DateUtils.truncate(new Date(), Calendar.DATE));
			plan.setToTime(DateUtils.addDays(plan.getFromTime(), 1));
			plan.setName("无线质量分培养 - " + entity.getAdgroup().getProductName());
			plan.setPeriod(60);
			plan.setPremiumType(DevelopmentPlan.PremiumType.digit);
			plan.setPremium(BigDecimal.valueOf(0.1d));
			plan.setStatus(DevelopmentPlan.Status.starting);
			plan.setUserID(entity.getAdgroup().getUserID());
			plan.setDevice(1);
			plan.setNextTime(DateUtils.addMinutes(new Date(), plan.getPeriod()));
			if(entity.getAdgroup().getLimit() > 0){
				plan.setLimitValve(BigDecimal.valueOf(((float) entity.getAdgroup().getLimit()) / 100));
			}else if(entity.getQueue().getMaxPrice() > 0){
				plan.setLimitValve(BigDecimal.valueOf(((float) entity.getQueue().getMaxPrice()) / 100));
			}else if(report != null && report.getCpc() != null){
				plan.setLimitValve(report.getCpc().multiply(BigDecimal.valueOf(3)));
			}
			
			plan.setCreatedTime(new Date());
			plan.setModifiedTime(new Date());
			this.developmentPlanRepository.save(plan);
			for(DevelopmentKeyword dk: dks){
				dk.setPlanID(plan.getAutoID());
			}
			this.developmentKeywordRepository.save(dks);
			actionLog.setPlan(plan);
			entity.setPriced(true);
		}
		
		this.mobileQscoreActionLogRepository.save(actionLog);
		return true;
	}

	@Override
	public boolean mark(MobileQscoreSustainer entity) {
		SinbadClient.AdgroupDto dto = new SinbadClient.AdgroupDto();
		dto.setStatus(0);
		dto.setDriveType(-1);
		dto.setMaintainedDate(new Date());
		if(entity.isAdded() || entity.isPriced() || entity.isRemoved()){
			dto.setAdjustCount(entity.getAdgroup().getAdjustCount() + 1);
		}
		this.sinbadClient.updateAdgroup(entity.getAdgroup().getAdgroupID(), dto);
		
		if(entity.getDays() == 5){
			SinbadClient.CampaignDto campaignDto = new SinbadClient.CampaignDto();
			String schedulePreDay = "00:00-01:00:50,01:00-07:00:30,07:00-08:30:60,08:30-17:00:90,17:00-19:00:70,20:00-23:00:110,23:00-24:00:50";
			StringBuffer buffer = new StringBuffer();
			for(int i=1; i<=7;i++){
				buffer.append(schedulePreDay);
				if(i < 7){
					buffer.append(";");
				}
			}
			campaignDto.setSchdule(buffer.toString());
			campaignDto.setUserID(entity.getAdgroup().getUserID());
			campaignDto = this.sinbadClient.updateCampaign(entity.getAdgroup().getCampaignID(), campaignDto);
			if(campaignDto.getCampaignID() > 0){
				log.debug("success to update campaigin: {}", campaignDto);
			}else{
				log.debug("fail to update campaigin: {}", entity.getAdgroup());
			}
		}
		
		return true;
	}
	
	private SustainRule<MobileScoreKeywordSustainer> findRule(MobileScoreKeywordSustainer kwSustainer){
		for(SustainRule<MobileScoreKeywordSustainer> rule: this.sustainRules){
			if(rule.check(kwSustainer)){
				log.debug("keyword's mobile qscore sustain rule: {} - {}", rule.getClass().getName(), kwSustainer.getKeyword());
				return rule;
			}
		}
		log.warn("can't find keyword's mobile qscore sustain rule: {}", kwSustainer.getKeyword());
		return null;
	}

}
